#import <stdio.h>
#import <stdlib.h>
#include "config.h"
#include "dmcache.h"
#include "simdatatypes.h"
#include "simutils.h"

#define DEBUG

void handleInstruction(instruction* instr, results_s* results);
void checkInstructionCache(instruction* instr, results_s* results);
void checkDataCache(instruction* instr, results_s* results);
cache_s L1Icache;
cache_s L1Dcache;
cache_s L2cache;
unsigned long numInstr=0;
ulonglong currTick=0;
char* instrTypes[] =
{
	"L",
	"S",
	"B",
	"C"
};
//#define TRACEPRINT
int main(int argc, char **argv)
{
	instruction currentInstr;
	static results_s results;
	initResults(&results);
	//char * fileString={"./short-traces/I10"};
	char * fileString={"stdin"};
	/*Begin Simulation*/
	printf("ECEE 4593 Cache Simulator Project\n");
	if (readConfigFile("./src/parametersBase.conf") != 0)
	{
		return 1;
	}
	printf("Using Trace File %s\n",fileString);
	if (openTraceFile(fileString) != 0)
	{
		printf("Unable to open Tracefile\n");
		return 1;
	}

	/*Create and initialize caches*/
	if(initCaches() != TRUE)
	{
		printf("Couldn't init Caches\n");
	}

	while(getInstruction(&currentInstr,&results) == 0)
	{
		handleInstruction(&currentInstr,&results);
		numInstr++;

	}

	printResults(&results);
	/*Cleanup all caches*/
	if( deleteCaches() != TRUE)
	{
		printf("Couldn't Delete All Caches\n");
	}
	printCache(&L1Icache);
	printCache(&L1Dcache);
	printCache(&L2cache);
	printf("\n===============================================\n");
	printf("\nCACHE SIMULATION COMPLETE\n");
	printf("\n===============================================\n");
	return 0;
}

void handleInstruction(instruction* instr, results_s* results){
	ulonglong temptick=0;
	//printInstruction(instr);
	PRINT("---------------------------------------------------------\n");
	PRINT("Inst: %ld Addr = %x, Type = %s, Exec = %x \n",
			numInstr,instr->pc,instrTypes[instr->opType],instr->exInfo);
	switch(instr->opType){
		case TO_Load:
			/*Check L1 Cache for instruction*/
			temptick = results->executeTime;
			checkInstructionCache(instr,results);
			/*Check L1 Cache for Data*/
			checkDataCache(instr,results);
			temptick = results->executeTime-temptick;
			results->cycleInstr.cycleLoad += temptick;
			results->cycleInstr.cycleTotal += temptick;
			break;
		case TO_Store:
			/*Check L1 Cache for instruction*/
			temptick = results->executeTime;
			checkInstructionCache(instr,results);
			/*Check L1 Cache for Data*/
			checkDataCache(instr,results);
			temptick = results->executeTime-temptick;
			results->cycleInstr.cycleStore += temptick;
			results->cycleInstr.cycleTotal += temptick;
			break;
		case TO_Computation:
			/*Handle a computation instruction*/
			/*Check L1 Cache for instruction*/
			temptick = results->executeTime;
			checkInstructionCache(instr,results);

			PRINT("Add exec time (+%d)\n",instr->exInfo.compLatency);

			results->executeTime += (instr->exInfo.compLatency);

			temptick = results->executeTime-temptick;
			results->cycleInstr.cycleComp += temptick;
			results->cycleInstr.cycleTotal += temptick;
			break;
		case TO_Branch:
			/*Handle a branch instruction*/
			/*Check L1 Cache for instruction*/
			temptick = results->executeTime;
			checkInstructionCache(instr,results);

			PRINT("Add exec time (+%d)\n",1);

			results->executeTime++;

			temptick = results->executeTime-temptick;
			results->cycleInstr.cycleBranch += temptick;
			results->cycleInstr.cycleTotal += temptick;
			break;
		default:
			fprintf(stderr,"opType is not an instruction:%d\n",instr->opType);
			break;
	}
	currTick+=temptick;

	PRINT("Simulated Time = %lld\n",currTick);

}
void checkInstructionCache(instruction* instr, results_s* results)
{
	kickout ret;
	/*Check L1 Cache for instruction*/
	if (!checkCache(&L1Icache, (uint32)instr->pc, instr->opType, results)){
		/*Add L1 Miss Time*/

		PRINT("L1miss (+%d)\n",L1_miss_time);

		results->executeTime += L1_miss_time;
/*Begin of Fetch*/
		/*Bring from l2 to l1*/
		if((ret=addValueToCache(&L1Icache,(uint32)instr->pc, instr->opType, results)).type == DIRTY){

			PRINT("Unable to add to L1I\n");
		}
/*end of fetch*/
		if(!checkCache(&L2cache,(uint32)instr->pc, instr->opType, results)){
			/*Add L2 Miss Time*/

			PRINT("L2miss (+%d)\n",L2_miss_time);

			results->executeTime += L2_miss_time;

			/*Bring in to L2 Cache*/
			if((ret = addValueToCache(&L2cache,(uint32)instr->pc, instr->opType, results)).type == DIRTY){

				PRINT("Doing Dirty Writeback for L2 Evicted:%x\n",ret.evictedAddress);
				/*Add memory to L2 transfer time*/
				results->executeTime += (mem_sendaddr+mem_ready+(mem_chunktime*(L2_block_size/mem_chunksize)));
				PRINT("L2memxfer (+%d)\n",(mem_sendaddr+mem_ready+(mem_chunktime*(L2_block_size/mem_chunksize))));

				/*Add replay time for L2*/
				PRINT("L2_hit_time (+%d)\n",L2_hit_time);
				results->executeTime += L2_hit_time;

			}
			/*Add Transfer to L2 Count*/
			results->transfers[L2]++;

			PRINT("Bringing line into L2.\n");
			PRINT("L2memxfer (+%d)\n",(mem_sendaddr+mem_ready+(mem_chunktime*(L2_block_size/mem_chunksize))));

			/*Add memory to L2 transfer time*/
			results->executeTime += (mem_sendaddr+mem_ready+(mem_chunktime*(L2_block_size/mem_chunksize)));

			/*Add replay time for L2*/

			PRINT("L2_hit_time (+%d)\n",L2_hit_time);

			results->executeTime += L2_hit_time;
		}
		else{
			/*Add L2_hit_time*/

			PRINT("L2_hit_time (+%d)\n",L2_hit_time);

			results->executeTime += L2_hit_time;
		}
		/*Add Transfer to L1I Count*/
		results->transfers[L1I]++;

		PRINT("Bringing line into L1I.\n");

		/*Add L2 to L1I transfer time*/

		PRINT("L2->L1 xfer (+%d)\n",L2_transfer_time*(L1_block_size/L2_bus_width));

		results->executeTime += L2_transfer_time*(L1_block_size/L2_bus_width);

		/*Add L1 replay time*/

		PRINT("L1_hit_time (+%d)\n",L1_hit_time);

		results->executeTime += L1_hit_time;

	}
	else{/*L1Hit*/
		results->executeTime += L1_hit_time;

		PRINT("L1_hit_time (+%d)\n",L1_hit_time);

	}
}
void checkDataCache(instruction* instr, results_s* results)
{
	kickout ret;
	if (!checkCache(&L1Dcache, (uint32)instr->exInfo.ldstAddr, instr->opType, results)){
		/*Add L1 Miss Time*/
		results->executeTime += L1_miss_time;

		PRINT("L1miss (+%d)\n",L1_miss_time);
/*Begin of writeback*/
		/*Bring from l2 to l1*/
		if((ret=addValueToCache(&L1Dcache, (uint32)instr->exInfo.ldstAddr, instr->opType, results)).type == DIRTY)
		{
			PRINT("Doing Dirty Writeback evicted: %x\n",ret.evictedAddress);

			if(!checkCache(&L2cache, ret.evictedAddress, instr->opType, results)){

				/*Add L2 Miss Time*/
				PRINT("L2miss (+%d)\n",L2_miss_time);
				results->executeTime += L2_miss_time;

				/*Bring in to L2*/
				if((ret=addValueToCache(&L2cache, ret.evictedAddress, instr->opType, results)).type == DIRTY)
				{
					PRINT("Doing Dirty Writeback for L2 Evicted:%x\n",ret.evictedAddress);
					/*Add memory to L2 transfer time*/
					results->executeTime += (mem_sendaddr+mem_ready+(mem_chunktime*(L2_block_size/mem_chunksize)));
					PRINT("L2memxfer (+%d)\n",(mem_sendaddr+mem_ready+(mem_chunktime*(L2_block_size/mem_chunksize))));

					/*Add replay time for L2*/
					PRINT("L2_hit_time (+%d)\n",L2_hit_time);
					results->executeTime += L2_hit_time;
				}
				/*Add Transfer to L2 Count*/
				results->transfers[L2]++;
				PRINT("Bringing line into L2.\n");

				/*Add memory to L2 transfer time*/
				results->executeTime += (mem_sendaddr+mem_ready+(mem_chunktime*(L2_block_size/mem_chunksize)));
				PRINT("L2memxfer (+%d)\n",(mem_sendaddr+mem_ready+(mem_chunktime*(L2_block_size/mem_chunksize))));

				/*Add replay time for L2*/
				PRINT("L2_hit_time (+%d)\n",L2_hit_time);
				results->executeTime += L2_hit_time;
			}
			else{
				/*Add L2_hit_time*/
				results->executeTime += L2_hit_time;
				PRINT("L2_hit_time (+%d)\n",L2_hit_time);

				/*Add L2 to L1 transfer time*/
				results->executeTime += L2_transfer_time*(L1_block_size/L2_bus_width);
				PRINT("L2->L1 xfer (+%d)\n",L2_transfer_time*(L1_block_size/L2_bus_width));
			}
		}
/*End of writeback move*/
		if(!checkCache(&L2cache, (uint32)instr->exInfo.ldstAddr, instr->opType, results)){

			/*Add L2 Miss Time*/
			PRINT("L2miss (+%d)\n",L2_miss_time);
			results->executeTime += L2_miss_time;

			/*Bring in to L2*/
			if((ret=addValueToCache(&L2cache, (uint32)instr->exInfo.ldstAddr, instr->opType, results)).type == DIRTY)
			{
				PRINT("Doing Dirty Writeback for L2 Evicted:%x\n",ret.evictedAddress);
				/*Add memory to L2 transfer time*/
				results->executeTime += (mem_sendaddr+mem_ready+(mem_chunktime*(L2_block_size/mem_chunksize)));
				PRINT("L2memxfer (+%d)\n",(mem_sendaddr+mem_ready+(mem_chunktime*(L2_block_size/mem_chunksize))));

				/*Add replay time for L2*/
				PRINT("L2_hit_time (+%d)\n",L2_hit_time);
				results->executeTime += L2_hit_time;
			}
			/*Add Transfer to L2 Count*/
			results->transfers[L2]++;
			PRINT("Bringing line into L2.\n");

			/*Add memory to L2 transfer time*/
			results->executeTime += (mem_sendaddr+mem_ready+(mem_chunktime*(L2_block_size/mem_chunksize)));
			PRINT("L2memxfer (+%d)\n",(mem_sendaddr+mem_ready+(mem_chunktime*(L2_block_size/mem_chunksize))));

			/*Add replay time for L2*/
			PRINT("L2_hit_time (+%d)\n",L2_hit_time);
			results->executeTime += L2_hit_time;
		}
		else
		{
			/*Add L2_hit_time*/
			PRINT("L2_hit_time (+%d)\n",L2_hit_time);
			results->executeTime += L2_hit_time;
		}

		/*Add Transfer to L1D Count*/
		results->transfers[L1D]++;
		PRINT("Bringing line into L1D.\n");

		/*Add L2 to L1 transfer time*/
		results->executeTime += L2_transfer_time*(L1_block_size/L2_bus_width);
		PRINT("L2->L1 xfer (+%d)\n",L2_transfer_time*(L1_block_size/L2_bus_width));

		/*Add L1 replay time*/
		PRINT("L1_hit_time (+%d)\n",L1_hit_time);
		results->executeTime += L1_hit_time;

	}
	else{/*L1Hit*/

		PRINT("L1_hit_time (+%d)\n",L1_hit_time);

		results->executeTime+=L1_hit_time;
	}
}
